/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tencent.tendon.convert.json;

import java.lang.reflect.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *
 * @author nbzhang
 */
@SuppressWarnings("unchecked")
public final class JsonAttributes {

	private static final boolean canunsafe;

	private static final boolean jdksunsafe;

	static {
		boolean rs = false;
		boolean rs2 = false;
		try {
			Class clazz = Class.forName("sun.misc.Unsafe");
			rs = AtomicInteger.class.getDeclaredField("unsafe") != null;
			rs2 = clazz.getDeclaredMethod("getBoolean", Object.class, long.class) != null;
		} catch (Throwable t) {
		}
		canunsafe = rs;
		jdksunsafe = rs2;
	}

	private JsonAttributes() {
	}

	private static boolean cansafe(Class type) {
		return jdksunsafe || (type != boolean.class && type != byte.class
			&& type != short.class && type != char.class && type != float.class && type != double.class);
	}

	public static JsonAttribute createJsonAttribute(final AccessibleObject fitem) {
		return ((fitem instanceof Method) ? createJsonAttribute((Method) fitem) : createJsonAttribute((Field) fitem));
	}

	private static <T, V> JsonAttribute<T, V> createJsonAttribute(final Method method) {
		if (method == null) return null;
		final JsonRef ref = method.getAnnotation(JsonRef.class);
		final JsonField jf = JsonFactory.findField(method.getDeclaringClass(), realMethodName(method));
		final Class clazz = method.getDeclaringClass();
		final Class fieldtype = method.getName().startsWith("set") ? method.getParameterTypes()[0] : method.getReturnType();
		JsonAttribute result = null;
		if (canunsafe && cansafe(fieldtype) && (ref == null || !ref.indirect()) && (jf == null || !jf.indirect())) {
			try {
				Field field = null;
				Class clazz0 = clazz;
				String fname = method.getName().substring(method.getName().startsWith("is") ? 2 : 3);
				if (fname.length() > 1 && !(fname.charAt(1) >= 'A' && fname.charAt(1) <= 'Z')) {
					fname = Character.toLowerCase(fname.charAt(0)) + fname.substring(1);
				} else if (fname.length() == 1) {
					fname = "" + Character.toLowerCase(fname.charAt(0));
				}
				do {
					try {
						field = clazz0.getDeclaredField(fname);
						break;
					} catch (Exception e) {
					}
				} while ((clazz0 = clazz0.getSuperclass()) != Object.class);
				if (field != null) {
					return SunUnsafe.createJsonAttribute(ref, field);
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}
		if (result != null) return result;
		return new JsonReflectMethodJsonAttribute(method);
	}

	private static <T, V> JsonAttribute<T, V> createJsonAttribute(final Field field) {
		if (field == null) return null;
		if (canunsafe && cansafe(field.getType())) return SunUnsafe.createJsonAttribute(null, field);
		return new JsonReflectFieldJsonAttribute(field);
	}

	//----------------------------------------------------------------------------------------
	static String findFieldName(final Method method) {
		JsonRef ref = method.getAnnotation(JsonRef.class);
		if (ref != null && ref.alias().length() > 0) return ref.alias();
		JsonField jf = JsonFactory.findField(method.getDeclaringClass(), realMethodName(method));
		if (jf != null && jf.alias() != null && jf.alias().length() > 0) return jf.alias();
		return realMethodName(method);
	}

	static String findFieldName(final Field field) {
		JsonRef ref = field.getAnnotation(JsonRef.class);
		if (ref != null && ref.alias().length() > 0) return ref.alias();
		JsonField jf = JsonFactory.findField(field.getDeclaringClass(), field.getName());
		if (jf != null && jf.alias() != null && jf.alias().length() > 0) return jf.alias();
		return field.getName();
	}

	static Type findFieldType(final AccessibleObject fitem) {
		if (fitem instanceof Method) {
			Method method = (Method) fitem;
			return method.getName().startsWith("set") ? method.getGenericParameterTypes()[0] : method.getGenericReturnType();
		} else {
			Field field = (Field) fitem;
			return field.getGenericType();
		}
	}

	static String realMethodName(Method method) {
		String fname = method.getName();
		fname = fname.substring(fname.startsWith("is") ? 2 : 3);
		if (fname.length() > 1 && !(fname.charAt(1) >= 'A' && fname.charAt(1) <= 'Z')) {
			fname = Character.toLowerCase(fname.charAt(0)) + fname.substring(1);
		} else if (fname.length() == 1) {
			fname = "" + Character.toLowerCase(fname.charAt(0));
		}
		return fname;
	}

	//----------------------------------------------------------------------------------------
	private static final class SunUnsafe {

		private static final sun.misc.Unsafe unsafe;

		static {
			Object unsafe0 = null;
			try {
				java.lang.reflect.Field f = AtomicInteger.class.getDeclaredField("unsafe");
				f.setAccessible(true);
				unsafe0 = f.get(null);
			} catch (Throwable t) {
				t.printStackTrace();
			}
			unsafe = (sun.misc.Unsafe) unsafe0;
		}

		public static JsonAttribute createJsonAttribute(final JsonRef ref, Field field) {
			final long offset = unsafe.objectFieldOffset(field);
			final Class type = field.getType();
			if (canunsafe && cansafe(type)) {
				if (type == boolean.class) return new JsonSunBoolAttribute(findFieldName(field), offset);
				if (type == byte.class) return new JsonSunByteAttribute(findFieldName(field), offset);
				if (type == char.class) return new JsonSunCharAttribute(findFieldName(field), offset);
				if (type == short.class) return new JsonSunShortAttribute(findFieldName(field), offset);
				if (type == int.class) return new JsonSunIntAttribute(findFieldName(field), offset);
				if (type == float.class) return new JsonSunFloatAttribute(findFieldName(field), offset);
				if (type == long.class) return new JsonSunLongAttribute(findFieldName(field), offset);
				if (type == double.class) return new JsonSunDoubleAttribute(findFieldName(field), offset);
			}
			return new JsonSunObjectAttribute(findFieldName(field), offset);
		}
	}

	private static final class JsonReflectMethodJsonAttribute<T, V> implements JsonAttribute<T, V> {

		private final String fieldname;

		private final Method method;

		public JsonReflectMethodJsonAttribute(final Method method) {
			this.method = method;
			this.fieldname = findFieldName(method);
		}

		@Override
		public final V get(T obj) {
			try {
				return (V) method.invoke(obj);
			} catch (Exception ex) {
				throw new JsonException("get value from object(" + obj + ") error for method (" + method + ") ", ex);
			}
		}

		@Override
		public final void set(T obj, V value) {
			try {
				method.invoke(obj, value);
			} catch (Exception ex) {
				throw new JsonException("set value to object(" + obj + ") error for method (" + method + ") ", ex);
			}
		}

		@Override
		public String field() {
			return fieldname;
		}

		@Override
		public String toString() {
			return this.getClass().getSimpleName() + "{fieldname = " + fieldname + "}";
		}
	}

	private static final class JsonReflectFieldJsonAttribute<T, V> implements JsonAttribute<T, V> {

		private final String fieldname;

		private final Field field;

		public JsonReflectFieldJsonAttribute(final Field field) {
			this.field = field;
			this.fieldname = findFieldName(field);
		}

		@Override
		public final V get(T obj) {
			try {
				return (V) field.get(obj);
			} catch (Exception ex) {
				throw new JsonException("get value from object(" + obj + ") error for field (" + field + ") ", ex);
			}
		}

		@Override
		public final void set(T obj, V value) {
			try {
				field.set(obj, value);
			} catch (Exception ex) {
				throw new JsonException("set value to object(" + obj + ") error for field (" + field + ") ", ex);
			}
		}

		@Override
		public String field() {
			return this.fieldname;
		}

		@Override
		public String toString() {
			return this.getClass().getSimpleName() + "{fieldname = " + fieldname + "}";
		}
	}

	private static abstract class JsonSunAttribute<T, V> implements JsonAttribute<T, V> {

		protected static final sun.misc.Unsafe unsafe = SunUnsafe.unsafe;

		protected final String fieldname;

		protected final long offset;

		public JsonSunAttribute(String fieldname, final long offset) {
			this.offset = offset;
			this.fieldname = fieldname;
		}

		@Override
		public String field() {
			return this.fieldname;
		}

		@Override
		public String toString() {
			return this.getClass().getSimpleName() + "{fieldname = " + fieldname + "}";
		}
	}

	private static final class JsonSunObjectAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunObjectAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) unsafe.getObject(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putObject(obj, offset, value);
		}
	}

	private static final class JsonSunBoolAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunBoolAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Boolean) unsafe.getBoolean(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putBoolean(obj, offset, (Boolean) value);
		}
	}

	private static final class JsonSunByteAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunByteAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Byte) unsafe.getByte(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putByte(obj, offset, (Byte) value);
		}
	}

	private static final class JsonSunCharAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunCharAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Character) unsafe.getChar(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putChar(obj, offset, (Character) value);
		}
	}

	private static final class JsonSunShortAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunShortAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Short) unsafe.getShort(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putShort(obj, offset, (Short) value);
		}
	}

	private static final class JsonSunIntAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunIntAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Integer) unsafe.getInt(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putInt(obj, offset, (Integer) value);
		}
	}

	private static final class JsonSunFloatAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunFloatAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Float) unsafe.getFloat(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putFloat(obj, offset, (Float) value);
		}
	}

	private static final class JsonSunLongAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunLongAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Long) unsafe.getLong(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putLong(obj, offset, (Long) value);
		}
	}

	private static final class JsonSunDoubleAttribute<T, V> extends JsonSunAttribute<T, V> {

		public JsonSunDoubleAttribute(String fieldname, final long offset) {
			super(fieldname, offset);
		}

		@Override
		public final V get(T obj) {
			return (V) (Double) unsafe.getDouble(obj, offset);
		}

		@Override
		public final void set(T obj, V value) {
			unsafe.putDouble(obj, offset, (Double) value);
		}
	}
}
